﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Framework.Event;
using Framework.Repositories;
using Framework.Infrastructure;
using Framework.Bus;
using Framework.Infrastructure.IoC;
using Microsoft.Practices.Unity;
using System.Reflection;

namespace Framework
{
    public class EventEntity : IEntity
    {


        private readonly Dictionary<Type, List<IDomainEventHandler>> domainEventHandlers = new Dictionary<Type, List<IDomainEventHandler>>();
        /// <summary>
        /// Event handler is actived after UnitOfWork Commit by sync
        /// </summary>
        /// 

        IEventBus _eventBus;
        IEventBus EventBus
        {
            get
            {
                return _eventBus ?? (_eventBus = IoCFactory.Instance.CurrentContainer.Resolve<IEventBus>());
            }
        }


        protected virtual void SendEvent(IDomainEvent e, Action<dynamic> beforeCommitAction = null)
        {
            HandleEvent(e);
            e.Sender = this;
            EventBus.Send(e, beforeCommitAction);
        }

        /// <summary>
        /// Event handler is actived after UnitOfWork Commit by async
        /// </summary>
        protected virtual void PostEvent(IDomainEvent e, Action<dynamic> beforeCommitAction = null)
        {
            EventBus.Post(e, beforeCommitAction);
        }


        private IEnumerable<IDomainEventHandler> GetDomainEventHandlers(IDomainEvent domainEvent)
        {
            Type eventType = domainEvent.GetType();
            if (domainEventHandlers.ContainsKey(eventType))
                return domainEventHandlers[eventType];
            else
            {
                List<IDomainEventHandler> handlers = new List<IDomainEventHandler>();
                // firstly create and add all the handler methods defined within the aggregation root.
                MethodInfo[] allMethods = this.GetType().GetMethods(BindingFlags.Public |
                    BindingFlags.NonPublic | BindingFlags.Instance);
                var handlerMethods = from method in allMethods
                                     let returnType = method.ReturnType
                                     let @params = method.GetParameters()
                                     let handlerAttributes = method.GetCustomAttributes(typeof(HandlesAttribute), false)
                                     where returnType == typeof(void) &&
                                     @params != null &&
                                     @params.Count() > 0 &&
                                     @params[0].ParameterType.Equals(eventType) &&
                                     handlerAttributes != null &&
                                     handlerAttributes.Length > 0 &&
                                     ((HandlesAttribute)handlerAttributes[0]).DomainEventType.Equals(eventType)
                                     select new { MethodInfo = method };
                foreach (var handlerMethod in handlerMethods)
                {
                    handlers.Add(new InlineDomainEventHandler(this, handlerMethod.MethodInfo));
                }
                // then read all the registered handlers.

                domainEventHandlers.Add(eventType, handlers);
                return handlers;
            }
        }

        private void HandleEvent<TEvent>(TEvent @event)
          where TEvent : IDomainEvent
        {
            var handlers = this.GetDomainEventHandlers(@event);
            foreach (var handler in handlers)
            {
                if (handler.CanHandle(@event))
                    handler.Handle(@event);
            }
        }
    }
}
